package com.kelvem.saas.workbanch.fsm.model;

import com.kelvem.saas.workbanch.fsm.entity.Entity;
import com.kelvem.saas.workbanch.core.utils.StringUtil;
import com.kelvem.saas.workbanch.fsm.entity.FsmEntity;
import com.kelvem.saas.workbanch.fsm.core.FsmException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

/**
 * FsmNode
 *
 * @author kelvem
 */
@Slf4j
@Data
public class FsmNode {
    
    private Class<? extends FsmEntity> entityClazz;
    private String entityName;
    private Class<? extends Entity> parentEntityClazz;
    private String parentEntityName;
    
    private List<StateNode> stateList = new ArrayList<StateNode>();
    private List<StateGroupNode> groupStateList = new ArrayList<StateGroupNode>();
    private List<EventNode> eventList = new ArrayList<EventNode>();
    
    private Map<String, StateNode> stateMap = new LinkedHashMap<String, StateNode>();
    private Map<String, StateGroupNode> groupStateMap = new LinkedHashMap<String, StateGroupNode>();
    private Map<String, List<String>> groupStateNameMapping = new HashMap<String, List<String>>();
    
    private Map<String, EventNode> eventMap = new LinkedHashMap<String, EventNode>();
    private Set<String> eventNameSet = new LinkedHashSet<String>();

    /**
     * @param entityClazz
     * @param parentEntityClazz
     */
    public FsmNode(Class<? extends FsmEntity> entityClazz, Class<? extends Entity> parentEntityClazz) {
        this.entityClazz = entityClazz;
        this.entityName = entityClazz.getSimpleName();
        this.setParentEntityClazz(parentEntityClazz);
    }
    
    public void addData(List<StateNode> stateList, List<StateGroupNode> groupStateList, List<EventNode> eventList) {
        
        /**
         * state
         */
        for (StateNode state :
                stateList) {
            if (StringUtil.isEmpty(state.getCode())) {
                String msg = String.format("Clazz[%s]的@EventState(stateCode=\"\", stateName=\"%s\"), stateCode为空, 请检查注解", state.getClazzSimpleName(), state.getName());
                log.error(msg);
                throw new FsmException(msg);
            }
            if (stateMap.containsKey(state.getCode().toLowerCase())) {
                StateNode oldState = stateMap.get(state.getCode().toLowerCase());
                String msg = String.format("Clazz[%s]的@EventState(stateCode=\"%s\"), 与 Clazz[%s]的@EventState(stateCode=\"%s\"), 重复状态名, 请检查注解",
                        oldState.getClazzSimpleName(), oldState.getCode(), state.getClazzSimpleName(), state.getCode());
                log.error(msg);
                throw new FsmException(msg);
            }
            stateMap.put(state.getCode().toLowerCase(), state);
        }
        this.stateList.addAll(stateList);
        
        /**
         * stateGroup
         */
        for (StateGroupNode group :
                groupStateList) {
            if (StringUtil.isEmpty(group.getCode())) {
                String msg = String.format("Clazz[%s]的@EventStateGroup(groupCode=\"\", groupName=\"%s\"), groupCode为空, 请检查注解", group.getClazzSimpleName(), group.getName());
                log.error(msg);
                throw new FsmException(msg);
            }
            if (groupStateMap.containsKey(group.getCode().toLowerCase())) {
                StateGroupNode oldGroup = groupStateMap.get(group.getCode().toLowerCase());
                String msg = String.format("Clazz[%s]的@EventStateGroup(groupCode=\"%s\"), 与 Clazz[%s]的@EventStateGroup(groupCode=\"%s\"), 重复状态组名, 请检查注解",
                        oldGroup.getClazzSimpleName(), oldGroup.getCode(), group.getClazzSimpleName(), group.getCode());
                log.error(msg);
                throw new FsmException(msg);
            }
            groupStateMap.put(group.getCode().toLowerCase(), group);
        }
        this.groupStateList.addAll(groupStateList);
        
        /**
         * event
         */
        for (EventNode event :
                eventList) {
            if (StringUtil.isEmpty(event.getCode())) {
                String msg = String.format("Clazz[%s]的@RegisterEvent(eventCode=\"\", eventName=\"%s\"), eventCode为空, 请检查注解", event.getClazzSimpleName(), event.getName());
                log.error(msg);
                throw new FsmException(msg);
            }
            if (eventMap.containsKey(getEventKey(event))) {
                EventNode oldEvent = eventMap.get(getEventKey(event));
                String msg = String.format("Clazz[%s]的@RegisterEvent(eventCode=\"%s\"), 与 Clazz[%s]的@RegisterEvent(eventCode=\"%s\"), 重复事件名, 请检查注解",
                        oldEvent.getClazzSimpleName(), oldEvent.getCode(), event.getClazzSimpleName(), event.getCode());
                log.error(msg);
                throw new FsmException(msg);
            }
    
            eventMap.put(getEventKey(event), event);
            eventNameSet.add(event.getCode().toLowerCase());
        }
        this.eventList.addAll(eventList);
        
    }

    //-----------------------------------------------------------
    //        log
    //-----------------------------------------------------------
    public void printLog() {
        log.info("状态机[{}] 信息如下 -------------------------------------", entityName);
        if (parentEntityName != null && parentEntityName.trim().length() > 0) {
            log.info("状态机[{}] 的父状态机[{}] ", entityName, parentEntityName);
        }
        
        StringBuilder sb = new StringBuilder();
        for (StateNode state : stateList) {
            sb.append(state.getCode()).append(", ");
        }
        log.info("状态机[{}] 包含的State : {}", entityName, sb.toString());
    
        sb = new StringBuilder();
        for (StateGroupNode group : groupStateList) {
            sb.append(group.getCode()).append(", ");
        }
        log.info("状态机[{}] 包含的GroupState : {}", entityName, sb.toString());
    
        sb = new StringBuilder();
        for (String name : eventNameSet) {
            sb.append(name).append(", ");
        }
        log.info("状态机[{}] 包含的Event : {}", entityName, sb.toString());
    
        log.info("状态机[{}] State信息如下>>>>>>", entityName);
        for (StateNode state : this.getStateList()) {
            log.info("状态机[{}] State[{}] : {}", entityName, state.getCode(), state);
        }
        log.info("状态机[{}] GroupState信息如下>>>>>>", entityName);
        for (String groupName : groupStateNameMapping.keySet()) {
            List<String> stateNameList = groupStateNameMapping.get(groupName);
            log.info("状态机[{}] GroupState[{}] : {}", entityName, groupName, StringUtil.toString(stateNameList, ","));
        }
        log.info("状态机[{}] Event信息如下>>>>>>", entityName);
        for (EventNode event : eventList) {
            log.info("状态机[{}] Event[{}] : {}", entityName, event.getCode(), event);
        }
    }
    
    //-----------------------------------------------------------
    //        解析状态组与状态的关系
    //-----------------------------------------------------------
    
    /**
     * reset
     */
    public void resetGroupStateNameMapping() {
        this.groupStateNameMapping = getSubStateNameMap(stateList, groupStateList);
    }
    
    /**
     * 获取StateGroup节点的subState Map<StateGroup, List<State>>
     *
     * @param stateList
     * @return Map<String,List<String>>
     * @throws
     */
    public Map<String, List<String>> getSubStateNameMap(List<StateNode> stateList, List<StateGroupNode> groupList) {
        
        try {
            Map<String, StateGroupNode> map = new LinkedHashMap<String, StateGroupNode>();
            
            // 读取state
            for (StateNode state : stateList) {
                map.put(state.getCode(), state);
            }
            // 读取stateGroup
            for (StateGroupNode stateGroup : groupList) {
                map.put(stateGroup.getCode(), stateGroup);
            }
            
            // result
            Map<String, List<String>> result = new LinkedHashMap<String, List<String>>();
            
            for (StateGroupNode stateGroup : groupList) {
                if (stateGroup.getStates().length <= 0) {
                    // stateGroup是否包含sub state
                    String msg = String.format("@EventStateGroup(groupCode=\"%s\", states={\"\"}), states节点不可以为空, 请检查注解", stateGroup.getCode());
                    log.error(msg);
                    throw new FsmException(msg);
                }
                
                Set<String> visitPath = new HashSet<String>();
                List<StateGroupNode> list = getStateGroupList(stateGroup, map, visitPath);
                
                List<String> subStateNameList = new ArrayList<String>();
                for (StateGroupNode subState : list) {
                    subStateNameList.add(subState.getCode());
                }
                result.put(stateGroup.getCode(), subStateNameList);
            }
            
            return result;
        } catch (Exception e) {
            log.error("获取StateGroup的subState err, root=", e);
            throw new FsmException("获取StateGroup的subState err, " + e.getMessage());
        }
    }
    
    
    /**
     * 递归方法 获取StateGroup节点的subState
     *
     * @param stateGroup stateGroup节点
     * @param stateGroupMap 全部state与stateGroup节点的Map
     * @param visitPath 访问路径, 记录递归调用的stateGroup名, 防止无限循环引用
     * @return List<StateGroupNode>
     * @throws
     */
    private List<StateGroupNode> getStateGroupList(StateGroupNode stateGroup, Map<String, StateGroupNode> stateGroupMap, Set<String> visitPath) {
        
        List<StateGroupNode> result = new ArrayList<StateGroupNode>();
        
        String[] subStateList = stateGroup.getStates();
        if (subStateList == null || subStateList.length <= 0) {
            // state type
            result.add(stateGroup);
            return result;
        } else {
            
            if (visitPath.contains(stateGroup.getCode())) {
                // 循环引用, 可以考虑抛异常 或 忽略该错误可返回一个空List
                String msg = String.format("@EventStateGroup(groupCode=\"%s\"), states节点存在无限循环引用, 请检查注解", stateGroup.getCode());
                log.error(msg);
                throw new FsmException(msg);
            }
            
            // state group type
            for (String subStateCode : subStateList) {
                StateGroupNode subStateGroup = stateGroupMap.get(subStateCode);
                if (subStateGroup == null && StringUtil.isEmpty(subStateCode)) {
                    // stateGroupMap是否存在
                    String msg = String.format("@EventStateGroup(groupCode=\"%s\", states={\"...\", \"\"}), states内的状态名不能为空白, 请检查注解", stateGroup.getCode());
                    log.error(msg);
                    throw new FsmException(msg);
                }
                if (subStateGroup == null) {
                    // stateGroupMap是否存在
                    String msg = String.format("@EventStateGroup(groupCode=\"%s\", states={\"...\", \"%s\"}), 未识别的状态名%s, 请确认是否存在注解 @EventState(stateCode=\"%s\")", stateGroup.getCode(), subStateCode, subStateCode, subStateCode);
                    log.error(msg);
                    throw new FsmException(msg);
                }
                
                List<StateGroupNode> buf = getStateGroupList(subStateGroup, stateGroupMap, visitPath);
                result.addAll(buf);
                visitPath.add(stateGroup.getCode());
            }
        }
        
        return result;
    }
    
    //-----------------------------------------------------------
    //        public
    //-----------------------------------------------------------
    
    public List<String> getEventStateList(String state) {
        List<String> result = new ArrayList<String>();
        if (this.stateMap.containsKey(state.toLowerCase())) {
            result.add(state.toLowerCase());
        } else if (this.groupStateMap.containsKey(state.toLowerCase())) {
            result = this.getGroupStateNameMapping().get(state.toLowerCase());
        }
        return result;
    }
    
    //-----------------------------------------------------------
    //        private
    //-----------------------------------------------------------
    
    private String getEventKey(EventNode event) {
        String key = event.getEntityName() + "-" + event.getSrcState().toLowerCase() + "-" + event.getCode().toLowerCase();
        return key;
    }
    
    //-----------------------------------------------------------
    //        get / set
    //-----------------------------------------------------------
    public void setParentEntityClazz(Class<? extends Entity> parentEntityClazz) {
        if (parentEntityClazz != null && !parentEntityClazz.equals(Entity.class)) {
            this.parentEntityClazz = parentEntityClazz;
        } else {
            this.parentEntityClazz = null;
        }
        if (this.parentEntityClazz != null) {
            this.parentEntityName = this.parentEntityClazz.getSimpleName();
        } else {
            this.parentEntityName = null;
        }
    }
    
    public void setParentEntityName(String parentEntityName) {
        throw new FsmException("不支持setParentEntityName方法");
    }

}

